namespace Microsoft.ServiceModel.Samples.Discovery
{
    using System;
    using System.Xml;
    using System.Threading;
    using System.Collections.ObjectModel;
    using System.ServiceModel;
    using System.ServiceModel.Description;
    using Microsoft.ServiceModel.Samples.Discovery.Protocol;
    using System.Collections.Generic;
    using System.Diagnostics;
    
    public class ServicePublisher : IDisposable, IFindRequestHandler
    {
        bool disposed;
        Announcer announcer;
        FindListener findListener;
        IServicePropertiesMatcher servicePropertiesMatcher;
        SynchronizedCollection<ServiceProperties> publishedProperties;
        object thisLock;

        public ServicePublisher()
            : this(ProtocolUtility.CreateMulticastOutputBinding(),
                   ProtocolUtility.CreateMulticastDuplexInputBinding())
                    
        {
        }

        public ServicePublisher(OutputBinding outputBinding, 
            InputBinding inputBinding)
            : this(outputBinding, inputBinding, new ServicePropertiesMatcher())
        {
        }

        public ServicePublisher(OutputBinding outputBinding, 
            InputBinding inputBinding, 
            IServicePropertiesMatcher servicePropertiesMatcher)
        {
            Initialize(outputBinding, inputBinding, servicePropertiesMatcher);
        }

        void Initialize(OutputBinding outputBinding, InputBinding inputBinding,
            IServicePropertiesMatcher servicePropertiesMatcher)
        {
            if (servicePropertiesMatcher == null)
                throw new ArgumentNullException("servicePropertiesMatcher");

            this.disposed = false;
            this.thisLock = new object();
            this.publishedProperties = 
                new SynchronizedCollection<ServiceProperties>(thisLock);
            this.servicePropertiesMatcher = servicePropertiesMatcher;
            this.announcer = new Announcer(outputBinding);
            this.findListener = new FindListener(this, inputBinding);
        }

    
        #region IFindRequestHandler Members

        public void HandleRequest(ProbeRequest request)
        {
            Debug.Assert(request != null);

            if (request != null)
            {
                List<ServiceProperties> matches = FindMatch(request.Probe);
                if (matches.Count != 0)
                    request.Reply(matches);
            }
        }

        public void HandleRequest(ResolveRequest request)
        {
            Debug.Assert(request != null);

            if (request != null)
            {
                ServiceProperties match = FindMatch(request.Resolve.EndpointAddress);
                if (match != null)
                    request.Reply(match);
            }
        }

        #endregion

        public void Publish(ServiceProperties properties)
        {
            if (properties == null)
                throw new ArgumentNullException("properties");

            // 1. Send hello.
            this.announcer.SendHello(properties);

            // 2. Add to the published services list
            if (properties.Scopes != null)
            {
                properties.CompiledScopes = 
                    ScopeCompiler.Compile(properties.Scopes);

                properties.CompiledScopesSpecified = true;
            }
            else
                properties.CompiledScopes = null;

            this.publishedProperties.Add(properties);
        }

        public bool Unpublish(ServiceProperties properties)
        {
            if (properties == null)
                throw new ArgumentNullException("properties");

            // 1. Check if the service is published.
            if (this.publishedProperties.Remove(properties))
            {
                this.announcer.SendBye(properties);
                return true;
            }
            else
            {
                return false;
            }
        }

        public bool Unpublish(EndpointAddress address)
        {
            if (address == null)
                throw new ArgumentNullException("address");

            ServiceProperties props = FindMatch(address);
            if (props != null)
                return Unpublish(props);
            else
                return false;
        }

        List<ServiceProperties> FindMatch(MatchCriteria criteria)
        {
            ServiceProperties[] currentProps = GetSnapShot();
            List<ServiceProperties> matchingProps = 
                new List<ServiceProperties>();

            for (int i = 0; i < currentProps.Length; i++)
            {
                if (this.servicePropertiesMatcher.IsMatch(currentProps[i], 
                    criteria))
                {
                    matchingProps.Add(currentProps[i]);
                }
            }

            return matchingProps;
        }

        ServiceProperties FindMatch(EndpointAddress address)
        {
            ServiceProperties match = null;
            ServiceProperties[] currentProps = GetSnapShot();
            for (int i = 0; i < currentProps.Length; i++)
            {
                if (this.servicePropertiesMatcher.IsMatch(currentProps[i], 
                    address))
                {
                    match = currentProps[i];
                    break;
                }
            }

            return match;
        }

        ServiceProperties[] GetSnapShot()
        {
            ServiceProperties[] currentProps = null;
            lock (thisLock)
            {
                currentProps = 
                    new ServiceProperties[this.publishedProperties.Count];

                this.publishedProperties.CopyTo(currentProps, 0);
            }

            return currentProps;
        }

        
        #region IDisposable Members

        
        public void Dispose()
        {
            if (!this.disposed)
            {
                if (this.findListener != null)
                    this.findListener.Dispose();

                if (this.announcer != null)
                    this.announcer.Dispose();

                if (this.publishedProperties != null)
                    this.publishedProperties.Clear();

                this.disposed = true;
            }
        }

        #endregion
    }
}
